﻿//-----------------------------------------------------------------------
// <copyright file="ClientSocket.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings

using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using GateShift.Common.Networking.Enumerations;
using GateShift.Common.Networking.Events;
using GateShift.Common.Networking.Structures;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.GZip;

#endregion

namespace GateShift.Client.Libraries.Networking
{
    /// <summary>
    /// Standard client networking class
    /// </summary>
    public class ClientSocket
    {
        //// See Codes: http://msdn.microsoft.com/en-us/library/ms740668.aspx

        #region Variables
        private readonly object _syncObject;
        private Socket _tcpSocket;
        private bool _firedDisconnection;
        private SymmetricAlgorithm _encryption;
        #endregion

        #region CTor/DTor

        /// <summary>
        /// Initializes a new instance of the ClientSocket class
        /// </summary>
        public ClientSocket()
        {
            _syncObject = new object();
        }

        #endregion

        #region Public Events

        /// <summary>
        /// Event raised when a connection to the server succeeds
        /// </summary>
        public event EventHandler Connected;

        /// <summary>
        /// Event raised when a connection to the server fails.
        /// </summary>
        public event EventHandler<ConnectionFailedEventArgs> ConnectFailed;

        /// <summary>
        /// Event raised when the connection to the server has been terminated
        /// </summary>
        public event EventHandler Disconnected;

        /// <summary>
        /// Event raised when data is about to be sent to the server
        /// </summary>
        public event EventHandler<SendBeginEventArgs> SendBegin;

        /// <summary>
        /// Event raised when data has been sucessfully sent to the server
        /// </summary>
        public event EventHandler<SendCompleteEventArgs> SendComplete;

        /// <summary>
        /// Event raised when data is about to be recieved from the server
        /// </summary>
        public event EventHandler<RecieveBeginEventArgs> RecieveBegin;

        /// <summary>
        /// Event raised when data has been recieved from the server
        /// </summary>
        public event EventHandler<RecieveCompleteEventArgs> RecieveComplete;

        /// <summary>
        /// Event raised when a complete set of data has been recieved from the server and is ready to be handled
        /// </summary>
        public event EventHandler<DataRecievedEventArgs> PacketRecieved;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether or not to compress all traffic sent to the server
        /// </summary>
        public bool CompressTraffic { get; set; }

        /// <summary>
        /// Gets or sets the destination IP and port to connect to. Can be IPv4 or IPv6
        /// </summary>
        public IPEndPoint ConnectionEndPoint { get; set; }

        /// <summary>
        /// Gets a value indicating whether or not there is an active connection
        /// </summary>
        public bool IsConnected
        {
            get
            {
                if (_tcpSocket == null)
                {
                    return false;
                }

                try
                {
                    return _tcpSocket.Connected;
                }
                catch
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Gets the local IP and Port the connection is bound to. Returns NULL if there is no connection.
        /// </summary>
        public IPEndPoint LocalEndPoint
        {
            get
            {
                if (_tcpSocket == null)
                {
                    return null;
                }

                try
                {
                    return _tcpSocket.LocalEndPoint as IPEndPoint;
                }
                catch
                {
                    return null;
                }
            }
        }

        #endregion

        #region Encryption/Decryption

        /// <summary>
        /// Sets the algorithm to encrypt by based on the configuration section in the config file.
        /// This can be a default name from .NET types or a custom implementation.
        /// </summary>
        /// <param name="algorithmName">Config name from http://msdn.microsoft.com/en-us/library/system.security.cryptography.cryptoconfig.aspx</param>
        /// <param name="key">Encryption Key to use</param>
        /// <param name="iv">Encryption Initialization Vector to use</param>
        public void SetEncryption(string algorithmName, byte[] key, byte[] iv)
        {
            if (_encryption != null)
            {
                _encryption.Clear();
            }

            _encryption = SymmetricAlgorithm.Create(algorithmName);

            _encryption.Padding = PaddingMode.ISO10126;
            _encryption.Key = key;
            _encryption.IV = iv;
        }

        #endregion

        #region Connection Methods

        /// <summary>
        /// Connect to the server from the endpoint specified in ConnectionEndPoint
        /// </summary>
        public void Connect()
        {
            // Make sure we are not already connected
            if (IsConnected)
            {
                throw new InvalidOperationException("Cannot connect when already connected to another endpoint");
            }

            // Make sure we have an endpoint to connect to
            if (ConnectionEndPoint == null)
            {
                throw new NullReferenceException("ConnectionEndPoint cannot be a null reference");
            }

            // Start with a new socket to allow for no cooldown time required (new local port)
            _tcpSocket = new Socket(ConnectionEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
                {
                    // The socket will linger for 10 seconds after 
                    // Socket.Close is called.
                    LingerState = new LingerOption(true, 10),

                    // Disable the Nagle Algorithm for this tcp socket.
                    NoDelay = true,

                    // Set the receive buffer size to 8k
                    ReceiveBufferSize = 8192,

                    // Set the send buffer size to 8k.
                    SendBufferSize = 8192,

                    // Set the Time To Live (TTL) to 42 router hops.
                    Ttl = 42
                };

            // Try to connect
            try
            {
                _tcpSocket.BeginConnect(ConnectionEndPoint, EndConnect, _tcpSocket);
            }
            catch (SocketException)
            {
                OnConnectFailed(new ConnectionFailedEventArgs(ConnectFailReason.Unknown));
            }
        }

        #endregion

        #region Disconnection Methods

        /// <summary>
        /// Disconnect client from the server
        /// </summary>
        public void Disconnect()
        {
            // If we are not connected there is no need to continue
            if (!IsConnected)
            {
                return;
            }

            // Try to disconnect
            try
            {
                _tcpSocket.Shutdown(SocketShutdown.Both);
                _tcpSocket.BeginDisconnect(false, EndDisconnect, _tcpSocket);
            }
            catch (SocketException)
            {
                return;
            }
        }

        #endregion

        #region Sending Methods

        /// <summary>
        /// Send information to the server using codes and content
        /// </summary>
        /// <param name="serviceID">ID of the Service the data relates to</param>
        /// <param name="serviceCode">Code for the Service the data relates to</param>
        /// <param name="data">Data for the service to handle. Can be NULL.</param>
        public void SendData(byte serviceID, byte serviceCode, byte[] data)
        {
            SendData(serviceID, serviceCode, data, false);
        }

        /// <summary>
        /// Send information to the server using codes and content
        /// </summary>
        /// <param name="serviceID">ID of the Service the data relates to</param>
        /// <param name="serviceCode">Code for the Service the data relates to</param>
        /// <param name="data">Data for the service to handle. Can be NULL.</param>
        /// <param name="encryptData">Boolean whether or not to encrypt traffic</param>
        public void SendData(byte serviceID, byte serviceCode, byte[] data, bool encryptData)
        {
            DataPacket packet = new DataPacket
                                    {
                                        PrimaryCode = serviceID,
                                        SecondaryCode = serviceCode,
                                        Flags = DataPacketFlags.None,
                                        Data = data
                                    };

            // Set Flags
            if (CompressTraffic)
            {
                packet.Flags |= DataPacketFlags.Compressed;
            }

            if (encryptData)
            {
                packet.Flags |= DataPacketFlags.Encrypted;
            }

            // Handle the encrypt/compression here
            packet = PreparePacket(packet);

            // Set length after preparing the packet
            packet.DataSize = (packet.Data == null) ? 0 : packet.Data.Length;

            // Send the built packet
            SendPacket(packet);
        } 

        #endregion

        #region Event Methods

        /// <summary>
        /// Calls the Connected event in a thread safe manner
        /// </summary>
        protected void OnConnected()
        {
            if (Connected == null)
            {
                return;
            }

            // Try a safe invoke for calls from a different thread
            ISynchronizeInvoke safeInvoker = Connected.Target as ISynchronizeInvoke;

            if (safeInvoker != null)
            {
                safeInvoker.Invoke(Connected, new object[] { this, EventArgs.Empty });
            }
            else
            {
                Connected.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Calls the ConnectFailed event in a thread safe manner
        /// </summary>
        /// <param name="e">ConnectionFailedEventArgs with information about the event</param>
        protected void OnConnectFailed(ConnectionFailedEventArgs e)
        {
            if (ConnectFailed == null)
            {
                return;
            }

            // Try a safe invoke for calls from a different thread
            ISynchronizeInvoke safeInvoker = ConnectFailed.Target as ISynchronizeInvoke;

            if (safeInvoker != null)
            {
                safeInvoker.Invoke(ConnectFailed, new object[] { this, e });
            }
            else
            {
                ConnectFailed.Invoke(this, e);
            }
        }

        /// <summary>
        /// Calls the Disconnected event in a thread safe manner
        /// </summary>
        protected void OnDisconnected()
        {
            if (Disconnected == null)
            {
                return;
            }

            // Try a safe invoke for calls from a different thread
            ISynchronizeInvoke safeInvoker = Disconnected.Target as ISynchronizeInvoke;

            if (safeInvoker != null)
            {
                safeInvoker.Invoke(Disconnected, new object[] { this, EventArgs.Empty });
            }
            else
            {
                Disconnected.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Calls the SendBegin event in a thread safe manner
        /// </summary>
        /// <param name="e">SendBeginEventArgs with information about the event</param>
        protected void OnSendBegin(SendBeginEventArgs e)
        {
            if (SendBegin == null)
            {
                return;
            }

            // Try a safe invoke for calls from a different thread
            ISynchronizeInvoke safeInvoker = SendBegin.Target as ISynchronizeInvoke;

            if (safeInvoker != null)
            {
                safeInvoker.Invoke(SendBegin, new object[] { this, e });
            }
            else
            {
                SendBegin.Invoke(this, e);
            }
        }

        /// <summary>
        /// Calls the SendComplete event in a thread safe manner
        /// </summary>
        /// <param name="e">SendCompleteEventArgs with information about the event</param>
        protected void OnSendComplete(SendCompleteEventArgs e)
        {
            if (SendComplete == null)
            {
                return;
            }

            // Try a safe invoke for calls from a different thread
            ISynchronizeInvoke safeInvoker = SendComplete.Target as ISynchronizeInvoke;

            if (safeInvoker != null)
            {
                safeInvoker.Invoke(SendComplete, new object[] { this, e });
            }
            else
            {
                SendComplete.Invoke(this, e);
            }
        }

        /// <summary>
        /// Calls the RecieveBegin event in a thread safe manner
        /// </summary>
        /// <param name="e">RecieveBeginEventArgs with information about the event</param>
        protected void OnRecieveBegin(RecieveBeginEventArgs e)
        {
            if (RecieveBegin == null)
            {
                return;
            }

            // Try a safe invoke for calls from a different thread
            ISynchronizeInvoke safeInvoker = RecieveBegin.Target as ISynchronizeInvoke;

            if (safeInvoker != null)
            {
                safeInvoker.Invoke(RecieveBegin, new object[] { this, e });
            }
            else
            {
                RecieveBegin.Invoke(this, e);
            }
        }

        /// <summary>
        /// Calls the RecieveComplete event in a thread safe manner
        /// </summary>
        /// <param name="e">RecieveCompleteEventArgs with information about the event</param>
        protected void OnRecieveComplete(RecieveCompleteEventArgs e)
        {
            if (RecieveComplete == null)
            {
                return;
            }

            // Try a safe invoke for calls from a different thread
            ISynchronizeInvoke safeInvoker = RecieveComplete.Target as ISynchronizeInvoke;

            if (safeInvoker != null)
            {
                safeInvoker.Invoke(RecieveComplete, new object[] { this, e });
            }
            else
            {
                RecieveComplete.Invoke(this, e);
            }
        }

        /// <summary>
        /// Calls the PacketRecieved event in a thread safe manner
        /// </summary>
        /// <param name="e">DataRecievedEventArgs with information about the event</param>
        protected void OnPacketRecieved(DataRecievedEventArgs e)
        {
            if (PacketRecieved == null)
            {
                return;
            }

            // Try a safe invoke for calls from a different thread
            ISynchronizeInvoke safeInvoker = PacketRecieved.Target as ISynchronizeInvoke;

            if (safeInvoker != null)
            {
                safeInvoker.Invoke(PacketRecieved, new object[] { this, e });
            }
            else
            {
                PacketRecieved.Invoke(this, e);
            }
        }

        #endregion

        #region Packet Modification

        /// <summary>
        /// Mdifies packets going to the server based on the flags
        /// </summary>
        /// <param name="packet">DataPacket to modify</param>
        /// <returns>Modified DataPacket</returns>
        private DataPacket PreparePacket(DataPacket packet)
        {
            // Return the packet as is when there is no data
            if (packet.Data == null)
            {
                return packet;
            }

            // Do we need to encrypt?
            if (((packet.Flags | DataPacketFlags.Encrypted) == packet.Flags) && _encryption != null)
            {
                // Create Streams
                MemoryStream baseStream = new MemoryStream();
                MemoryStream sourceStream = new MemoryStream(packet.Data);
                CryptoStream destinationStream = new CryptoStream(baseStream, _encryption.CreateEncryptor(), CryptoStreamMode.Write);

                // Transform Data
                StreamUtils.Copy(sourceStream, destinationStream, new byte[4096]);
                destinationStream.FlushFinalBlock();

                // Set transformed data
                packet.Data = baseStream.ToArray();

                // Close Streams
                sourceStream.Close();
                destinationStream.Close();
                baseStream.Close();
            }

            // Do we need to compress?
            if ((packet.Flags | DataPacketFlags.Compressed) == packet.Flags)
            {
                // Create Streams
                MemoryStream baseStream = new MemoryStream();
                MemoryStream sourceStream = new MemoryStream(packet.Data);
                GZipOutputStream destinationStream = new GZipOutputStream(baseStream);

                // Transform Data
                StreamUtils.Copy(sourceStream, destinationStream, new byte[4096]);
                destinationStream.Finish();

                // Set transformed data
                packet.Data = baseStream.ToArray();

                // Close Streams
                sourceStream.Close();
                destinationStream.Close();
                baseStream.Close();
            }

            return packet;
        }

        /// <summary>
        /// Unmodifies packets coming back from the server based on the flags
        /// </summary>
        /// <param name="packet">DataPacket to unmodify</param>
        /// <returns>Unmodified DataPacket</returns>
        private DataPacket UnpreparePacket(DataPacket packet)
        {
            // Return the packet as is when there is no data
            if (packet.Data == null)
            {
                return packet;
            }

            // Do we need to decompress?
            if ((packet.Flags | DataPacketFlags.Compressed) == packet.Flags)
            {
                // Create Streams
                MemoryStream baseStream = new MemoryStream(packet.Data);
                MemoryStream destinationStream = new MemoryStream();
                GZipInputStream sourceStream = new GZipInputStream(baseStream);

                // Transform Data
                StreamUtils.Copy(sourceStream, destinationStream, new byte[4096]);

                // Set transformed data
                packet.Data = destinationStream.ToArray();

                // Close Streams
                sourceStream.Close();
                destinationStream.Close();
                baseStream.Close();
            }

            // Do we need to encrypt?
            if (((packet.Flags | DataPacketFlags.Encrypted) == packet.Flags) && _encryption != null)
            {
                // Create Streams
                MemoryStream baseStream = new MemoryStream(packet.Data);
                MemoryStream destinationStream = new MemoryStream();
                CryptoStream sourceStream = new CryptoStream(baseStream, _encryption.CreateDecryptor(), CryptoStreamMode.Read);

                // Transform Data
                StreamUtils.Copy(sourceStream, destinationStream, new byte[4096]);

                // Set transformed data
                packet.Data = destinationStream.ToArray();

                // Close Streams
                sourceStream.Close();
                destinationStream.Close();
                baseStream.Close();
            }

            return packet;
        }

        #endregion

        #region Private Callback Methods

        #region Connection Methods

        /// <summary>
        /// Callback for finishing a connect request
        /// </summary>
        /// <param name="result">IAsyncResult with information about the event</param>
        private void EndConnect(IAsyncResult result)
        {
            // Make sure our socket isnt null
            if (_tcpSocket == null)
            {
                return;
            }

            // Try to send the data
            try
            {
                _tcpSocket.EndConnect(result);
            }
            catch (SocketException e)
            {
                // Handle specific errors
                switch (e.SocketErrorCode)
                {
                    case SocketError.TimedOut:
                        OnConnectFailed(new ConnectionFailedEventArgs(ConnectFailReason.ConnectionTimedOut));
                        break;
                    case SocketError.ConnectionRefused:
                        OnConnectFailed(new ConnectionFailedEventArgs(ConnectFailReason.EndPointNotFound));
                        break;
                    default:
                        throw;
                }

                return;
            }

            // Fire the event and reset our disconnect status
            lock (_syncObject)
            {
                _firedDisconnection = false;
                OnConnected();
            }

            // Start the Recieve
            BeginReceiveHeader();
        }

        #endregion

        #region Disconnection Methods

        /// <summary>
        /// Callback for finishing a disconnect request
        /// </summary>
        /// <param name="result">IAsyncResult with information about the event</param>
        private void EndDisconnect(IAsyncResult result)
        {
            // Complete the disconnect request.
            try
            {
                _tcpSocket.EndDisconnect(result);
            }
            catch (SocketException)
            {
                return;
            }

            // Finish and release resources
            FinalizeDisconnection();
        }

        /// <summary>
        /// Handles disconnect requests
        /// </summary>
        private void FinalizeDisconnection()
        {
            // Do not refire if we have already done so
            if (_firedDisconnection)
            {
                return;
            }

            // If we are still connected, we need to run a disconnect request first
            if (IsConnected)
            {
                Disconnect();
            }
            else
            {
                // Otherwise, we want to close the socket, notify, and set the flag
                lock (_syncObject)
                {
                    _firedDisconnection = true;
                    _tcpSocket.Close(20000);
                    _tcpSocket = null;
                    OnDisconnected();
                }
            }
        }

        #endregion

        #region Sending Methods

        /// <summary>
        /// Starts sending a DataPacket to the server
        /// </summary>
        /// <param name="packet">DataPacket to send</param>
        private void SendPacket(DataPacket packet)
        {
            // if we are not connected, do not continue
            if (!IsConnected)
            {
                return;
            }

            // Use a memory stream so we do not have to resize arrays
            MemoryStream outStream = new MemoryStream();

            // Write Flags
            outStream.WriteByte((byte)packet.Flags);

            // Write Codes
            outStream.WriteByte(packet.PrimaryCode);
            outStream.WriteByte(packet.SecondaryCode);

            // Write the size of the data
            outStream.Write(BitConverter.GetBytes(packet.DataSize), 0, 4);

            // Write the data, if any exists
            if (packet.Data != null)
            {
                outStream.Write(packet.Data, 0, packet.Data.Length);
            }

            // Notify we are starting the send
            OnSendBegin(new SendBeginEventArgs((int)outStream.Length));

            // Now make the call to send the data
            try
            {
                _tcpSocket.BeginSend(outStream.ToArray(), 0, (int)outStream.Length, SocketFlags.None, new AsyncCallback(EndSendPacket), packet);
            }
            catch (SocketException e)
            {
                // Handle specific errors
                switch (e.SocketErrorCode)
                {
                    case SocketError.ConnectionReset: // Forced Disconnect
                        FinalizeDisconnection();
                        break;
                    default: // Everything else, rethrow
                        throw;
                }
            }
        }

        /// <summary>
        /// Callback method for finishing sending of data
        /// </summary>
        /// <param name="result">IAsyncResult with information about the event</param>
        private void EndSendPacket(IAsyncResult result)
        {
            // if we are not connected, do not continue
            if (!IsConnected)
            {
                return;
            }

            int bytesSent = 0;

            // Try to send the data
            try
            {
                bytesSent = _tcpSocket.EndSend(result);
            }
            catch (SocketException e)
            {
                // Handle specific errors
                switch (e.SocketErrorCode)
                {
                    case SocketError.ConnectionReset: // Forced Disconnect
                        FinalizeDisconnection();
                        break;
                    default: // Everything else, rethrow
                        throw;
                }
            }

            // If we have none or less than none disconnect because that indicates a problem.
            // This method should not have been called with 0 unless the connection is dead
            if (bytesSent < 0)
            {
                Disconnect();
                return;
            }

            // Notify we sent X bytes
            OnSendComplete(new SendCompleteEventArgs(bytesSent));
        }

        #endregion

        #region Recieving Methods

        /// <summary>
        /// Method to start recieving data for the header of the packet
        /// </summary>
        private void BeginReceiveHeader()
        {
            // Create buffer
            byte[] receiveBuffer = new byte[7];

            // if we are not connected, do not continue
            if (!IsConnected)
            {
                return;
            }

            // Notify we are about to recieve data of X length
            OnRecieveBegin(new RecieveBeginEventArgs(7));

            // Now make the call to get the rest of the data
            try
            {
                _tcpSocket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, new AsyncCallback(EndReceiveHeader), receiveBuffer);
            }
            catch (SocketException e)
            {
                // Handle specific errors
                switch (e.SocketErrorCode)
                {
                    case SocketError.ConnectionReset: // Forced Disconnect
                        FinalizeDisconnection();
                        break;
                    default: // Everything else, rethrow
                        throw;
                }
            }
        }

        /// <summary>
        /// Callback method for finishing recieve for header
        /// </summary>
        /// <param name="result">IAsyncResult with information about the event</param>
        private void EndReceiveHeader(IAsyncResult result)
        {
            // Initialize local variables
            DataPacket packet = new DataPacket();
            byte[] receiveBuffer = (byte[])result.AsyncState;
            int bytesRecieved = 0;

            // if we are not connected, do not continue
            if (!IsConnected)
            {
                return;
            }

            // Try to get the header information
            try
            {
                bytesRecieved = _tcpSocket.EndReceive(result);
            }
            catch (SocketException e)
            {
                // Handle specific errors
                switch (e.SocketErrorCode)
                {
                    case SocketError.ConnectionReset: // Forced Disconnect
                        FinalizeDisconnection();
                        break;
                    default: // Everything else, rethrow
                        throw;
                }
            }

            // If we have none or less than none disconnect because that indicates a problem.
            // This method should not have been called with less than 7 unless the connection is dead
            if (bytesRecieved < 7)
            {
                Disconnect();
                return;
            }

            // Notify we have recieved data
            OnRecieveComplete(new RecieveCompleteEventArgs(bytesRecieved));

            // Set header information
            packet.Flags = (DataPacketFlags)receiveBuffer[0];
            packet.PrimaryCode = receiveBuffer[1];
            packet.SecondaryCode = receiveBuffer[2];
            packet.DataSize = BitConverter.ToInt32(receiveBuffer, 3);

            // Make sure there is content to Receive first
            if (packet.DataSize > 0)
            {
                // Now go and get the content part of the packet
                BeginReceiveContent(packet);
            }
            else
            {
                // Start listening again for the header
                BeginReceiveHeader();

                // Notify the user of the new packet information
                OnPacketRecieved(new DataRecievedEventArgs(packet));
            }
        }

        /// <summary>
        /// Method to start recieving data for the content of the packet
        /// </summary>
        /// <param name="data">DataPacket with information about the packet</param>
        private void BeginReceiveContent(DataPacket data)
        {
            // Create buffer
            data.Data = new byte[data.DataSize];

            // if we are not connected, do not continue
            if (!IsConnected)
            {
                return;
            }

            // Notify we are about to recieve data of X length
            OnRecieveBegin(new RecieveBeginEventArgs(data.DataSize));

            // Try to get the data from the socket
            try
            {
                _tcpSocket.BeginReceive(data.Data, 0, data.DataSize, SocketFlags.None, new AsyncCallback(EndReceiveContent), new RecieveState { BytesRecieved = 0, BytesLeft = data.DataSize, Packet = data });
            }
            catch (SocketException e)
            {
                // Handle specific errors
                switch (e.SocketErrorCode)
                {
                    case SocketError.ConnectionReset: // Forced Disconnect
                        FinalizeDisconnection();
                        break;
                    default: // Everything else, rethrow
                        throw;
                }
            }
        }

        /// <summary>
        /// Callback method for finishing recieve for content
        /// </summary>
        /// <param name="result">IAsyncResult with information about the event</param>
        private void EndReceiveContent(IAsyncResult result)
        {
            // Initialize local variables
            RecieveState state = (RecieveState)result.AsyncState;
            int bytesRecieved = 0;

            // if we are not connected, do not continue
            if (!IsConnected)
            {
                return;
            }

            // Try to get the data from the socket
            try
            {
                bytesRecieved = _tcpSocket.EndReceive(result);
            }
            catch (SocketException e)
            {
                // Handle specific errors
                switch (e.SocketErrorCode)
                {
                    case SocketError.ConnectionReset: // Forced Disconnect
                        FinalizeDisconnection();
                        break;
                    default: // Everything else, rethrow
                        throw;
                }
            }

            // Modify our current status
            state.BytesRecieved += bytesRecieved;
            state.BytesLeft -= bytesRecieved;

            // This method should not have been called with less than the data length unless the size
            // is more than what is currently in place. If that is the case, then we want to recieve more
            if (state.BytesLeft > 0 && bytesRecieved > 0)
            {
                try
                {
                    _tcpSocket.BeginReceive(state.Packet.Data, state.BytesRecieved, state.BytesLeft, SocketFlags.None, new AsyncCallback(EndReceiveContent), state);
                }
                catch (SocketException)
                {
                    Disconnect();
                }

                // Leave this method to continue recieving
                return;
            }

            // If we have none or less than none disconnect because that indicates a problem.
            // This method should not have been called with less than 0 unless the connection is dead
            if (bytesRecieved <= 0)
            {
                Disconnect();
                return;
            }

            // Notify we have recieved data
            OnRecieveComplete(new RecieveCompleteEventArgs(bytesRecieved));

            // Unencrypt/uncompress packet
            state.Packet = UnpreparePacket(state.Packet);

            // Start listening again
            BeginReceiveHeader();

            // Notify the user of the new packet information
            OnPacketRecieved(new DataRecievedEventArgs(state.Packet));
        }

        #endregion

        #endregion
    }
}